/*
 **     Query.cpp
 **
 **     Published / author: 2005-08-12 / grymse@alhem.net
 **/

/*
 * Copyright (C) 2001-2006 Anders Hedstrom
 * 
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along 
 * with this program; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 */
#ifdef _WIN32
#pragma warning(disable:4786)
#endif

#include <string>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>

#include "Database.h"
#include "Query.h"


#ifdef SQLITEW_NAMESPACE
namespace SQLITEW_NAMESPACE {
#endif


    Query::Query(Database & dbin):m_db(dbin), odb(dbin.grabdb()),
	res(NULL), row(false), cache_rc(0), cache_rc_valid(false),
	m_row_count(0), m_num_cols(0) {
    } Query::Query(Database & dbin,
		   const std::string & sql):m_db(dbin),
	odb(dbin.grabdb()), res(NULL), row(false), cache_rc(0),
	cache_rc_valid(false), m_row_count(0), m_num_cols(0) {
	execute(sql);
    }


    Query::~Query() {
	if (res) {
	    GetDatabase().error(*this, "sqlite3_finalize in destructor");
	    sqlite3_finalize(res);
	}
	if (odb) {
	    m_db.freedb(odb);
	}
    }


    Database & Query::GetDatabase()const {
	return m_db;
    }
    /*
     * The sqlite3_finalize() routine deallocates a prepared SQL
     * statement. All prepared statements must be finalized before the
     * database can be closed. 
     */ bool Query::execute(const std::string & sql) {
	// query,
	// no
	// result
	m_last_query = sql;
	if (odb && res) {
	    GetDatabase().error(*this, "execute: query busy");
	}
	if (odb && !res) {
	    const char *s = NULL;
	    int rc =
		sqlite3_prepare(odb->db, sql.c_str(), sql.size(), &res,
				&s);
	    if (rc != SQLITE_OK) {
		GetDatabase().error(*this,
				    "execute: prepare query failed");
		return false;
	    }
	    if (!res) {
		GetDatabase().error(*this, "execute: query failed");
		return false;
	    }
	    rc = sqlite3_step(res);	// execute
	    sqlite3_finalize(res);	// deallocate statement
	    res = NULL;
	    switch (rc) {
	    case SQLITE_BUSY:
		GetDatabase().error(*this, "execute: database busy");
		return false;
	    case SQLITE_DONE:
	    case SQLITE_ROW:
		return true;
	    case SQLITE_ERROR:
		GetDatabase().error(*this, sqlite3_errmsg(odb->db));
		return false;
	    case SQLITE_MISUSE:
		GetDatabase().error(*this, "execute: database misuse");
		return false;
	    }
	    GetDatabase().error(*this, "execute: unknown result code");
	}
	return false;
    }



    // methods using db specific api calls

    sqlite3_stmt *Query::get_result(const std::string & sql) {	// query, 
	// 
	// result
	if (odb && res) {
	    GetDatabase().error(*this, "get_result: query busy");
	}
	if (odb && !res) {
	    const char *s = NULL;
	    int rc =
		sqlite3_prepare(odb->db, sql.c_str(), sql.size(), &res,
				&s);
	    if (rc != SQLITE_OK) {
		GetDatabase().error(*this,
				    "get_result: prepare query failed");
		return NULL;
	    }
	    if (!res) {
		GetDatabase().error(*this, "get_result: query failed");
		return NULL;
	    }
	    // get column names from result
	    {
		int i = 0;
		do {
		    const char *p = sqlite3_column_name(res, i);
		    if (!p)
			break;
		    m_nmap[p] = ++i;
		}
		while (true);
		m_num_cols = i + 1;
	    }
	    cache_rc = sqlite3_step(res);
	    cache_rc_valid = true;
	    m_row_count = (cache_rc == SQLITE_ROW) ? 1 : 0;
	}
	return res;
    }


    void Query::free_result() {
	if (odb && res) {
	    sqlite3_finalize(res);
	    res = NULL;
	    row = false;
	    cache_rc_valid = false;
	}
	// clear column names
	while (m_nmap.size()) {
	    std::map < std::string, int >::iterator it = m_nmap.begin();
	    m_nmap.erase(it);
	}
    }


    bool Query::fetch_row() {
	rowcount = 0;
	row = false;
	if (odb && res) {
	    int rc = cache_rc_valid ? cache_rc : sqlite3_step(res);	// execute
	    cache_rc_valid = false;
	    switch (rc) {
	    case SQLITE_BUSY:
		GetDatabase().error(*this, "execute: database busy");
		return false;
	    case SQLITE_DONE:
		return false;
	    case SQLITE_ROW:
		row = true;
		return true;
	    case SQLITE_ERROR:
		GetDatabase().error(*this, sqlite3_errmsg(odb->db));
		return false;
	    case SQLITE_MISUSE:
		GetDatabase().error(*this, "execute: database misuse");
		return false;
	    }
	    GetDatabase().error(*this, "execute: unknown result code");
	}
	return false;
    }


    sqlite_int64 Query::insert_id() {
	if (odb) {
	    return sqlite3_last_insert_rowid(odb->db);
	} else {
	    return 0;
	}
    }


    long Query::num_rows() {
	return odb && res ? m_row_count : 0;
    }


    bool Query::is_null(int x) {
	if (odb && res && row) {
	    if (sqlite3_column_type(res, x) == SQLITE_NULL)
		return true;
	}
	return false;		// ...
    }


    const char *Query::get_str(const std::string & x) {
	int index = m_nmap[x] - 1;
	if (index >= 0)
	    return get_str(index);
	error("Column name lookup failure: " + x);
	return "";
    }


    const char *Query::get_str(int x) {
	if (odb && res && row && x < sqlite3_column_count(res)) {
	    const unsigned char *tmp = sqlite3_column_text(res, x);
	    return tmp ? (const char *) tmp : "";
	}
	return "";
    }


    const char *Query::get_str() {
	return get_str(rowcount++);
    }


    double Query::getnum(const std::string & x) {
	int index = m_nmap[x] - 1;
	if (index >= 0)
	    return getnum(index);
	error("Column name lookup failure: " + x);
	return 0;
    }


    double Query::getnum(int x) {
	if (odb && res && row) {
	    return sqlite3_column_double(res, x);
	}
	return 0;
    }


    long Query::getval(const std::string & x) {
	int index = m_nmap[x] - 1;
	if (index >= 0)
	    return getval(index);
	error("Column name lookup failure: " + x);
	return 0;
    }


    long Query::getval(int x) {
	if (odb && res && row) {
	    return sqlite3_column_int(res, x);
	}
	return 0;
    }


    double Query::getnum() {
	return getnum(rowcount++);
    }


    long Query::getval() {
	return getval(rowcount++);
    }


    unsigned long Query::getuval(const std::string & x) {
	int index = m_nmap[x] - 1;
	if (index >= 0)
	    return getuval(index);
	error("Column name lookup failure: " + x);
	return 0;
    }


    unsigned long Query::getuval(int x) {
	unsigned long l = 0;
	if (odb && res && row) {
	    l = sqlite3_column_int(res, x);
	}
	return l;
    }


    unsigned long Query::getuval() {
	return getuval(rowcount++);
    }


    int64_t Query::getbigint(const std::string & x) {
	int index = m_nmap[x] - 1;
	if (index >= 0)
	    return getbigint(index);
	error("Column name lookup failure: " + x);
	return 0;
    }


    int64_t Query::getbigint(int x) {
	if (odb && res && row) {
	    return sqlite3_column_int64(res, x);
	}
	return 0;
    }


    int64_t Query::getbigint() {
	return getbigint(rowcount++);
    }


    uint64_t Query::getubigint(const std::string & x) {
	int index = m_nmap[x] - 1;
	if (index >= 0)
	    return getubigint(index);
	error("Column name lookup failure: " + x);
	return 0;
    }


    uint64_t Query::getubigint(int x) {
	uint64_t l = 0;
	if (odb && res && row) {
	    l = sqlite3_column_int64(res, x);
	}
	return l;
    }


    uint64_t Query::getubigint() {
	return getubigint(rowcount++);
    }


    double Query::get_num(const std::string & sql) {
	double l = 0;
	if (get_result(sql)) {
	    if (fetch_row()) {
		l = getnum();
	    }
	    free_result();
	}
	return l;
    }


    long Query::get_count(const std::string & sql) {
	long l = 0;
	if (get_result(sql)) {
	    if (fetch_row())
		l = getval();
	    free_result();
	}
	return l;
    }


    const char *Query::get_string(const std::string & sql) {
	bool found = false;
	m_tmpstr = "";
	if (get_result(sql)) {
	    if (fetch_row()) {
		m_tmpstr = get_str();
		found = true;
	    }
	    free_result();
	}
	return m_tmpstr.c_str();	// %! changed from 1.0 which
	// didn't return NULL on failed
	// query
    }


    const std::string & Query::GetLastQuery() {
	return m_last_query;
    }


    std::string Query::GetError() {
	if (odb)
	    return sqlite3_errmsg(odb->db);
	return "";
    }


    int Query::GetErrno() {
	if (odb)
	    return sqlite3_errcode(odb->db);
	return 0;
    }


    bool Query::Connected() {
	return odb ? true : false;
    }




    void Query::ViewRes() {
	if (!res) {
	    printf("no result stored\n");
	    return;
	}
	printf("result column count = %d\n", sqlite3_column_count(res));
	for (int i = 0; i < sqlite3_column_count(res); i++) {
	    printf(" %2d   type %d   name '%s'", i,
		   sqlite3_column_type(res, i), sqlite3_column_name(res,
								    i));
	    printf("  / '%s'", (char *) sqlite3_column_text(res, i));
	    printf("  / %d", sqlite3_column_int(res, i));
	    printf("  / %f", sqlite3_column_double(res, i));
	    printf("\n");
	}
    }


    void Query::error(const std::string & msg) {
	GetDatabase().error(*this, msg);
    }


#ifdef SQLITEW_NAMESPACE
}				// namespace SQLITEW_NAMESPACE {
#endif
